package com.moon.game.client;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.FixedReceiveBufferSizePredictorFactory;
import org.jboss.netty.channel.socket.DatagramChannelFactory;
import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.serialization.ObjectDecoder;
import org.jboss.netty.handler.codec.serialization.ObjectEncoder;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

import com.moon.game.constants.GlobalConstants;
import com.moon.game.message.ObjMessage;
import com.moon.game.util.SizeCalculator;

public class UdpClient {
	private static final Logger log = Logger.getLogger(UdpClient.class);
	public static int CODEC_PROTOBUF = 1;
	public static int CODEC_STRING = 2;
	private Channel channel = null;
	private String ip;
	private int port;
	private int codec;
	private int sendBufferSize;
	private int recvBufferSize;

	/**
	 * 初始化连接参数
	 * 
	 * @param ip
	 * @param port
	 * @param bufferSize
	 * @param codec
	 */
	public UdpClient(String ip, int port, int codec, int sendBufferSize, int recvBufferSize) {
		this.ip = ip;
		this.port = port;
		this.codec = codec;
		this.sendBufferSize = sendBufferSize;
		this.recvBufferSize = recvBufferSize;
		connect();
	}

	/**
	 * 发送
	 * 
	 */
	public void send(ObjMessage data) {
		try {
			if (!channel.isConnected()) {
				connect();
			}
			if (SizeCalculator.calcSize(data) > sendBufferSize) {
				channel.close();
				log.error("send app data to large...");
				return;
			}
			channel.write(data);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	/**
	 * protocol
	 * 
	 * @author sky
	 * 
	 */
	private static class DataBufPipelineFactory implements ChannelPipelineFactory {
		public ChannelPipeline getPipeline() throws Exception {
			// Create a default pipeline implementation.
			ChannelPipeline pipeline = Channels.pipeline();
			pipeline.addLast("decoder", new ObjectEncoder());
			pipeline.addLast("encoder", new ObjectDecoder());
			pipeline.addLast("handler", new UdpClientHandler());
			return pipeline;
		}
	}

	/**
	 * 字符串
	 * 
	 * @author sky
	 * 
	 */
	private class StringPipelineFactory implements ChannelPipelineFactory {
		public ChannelPipeline getPipeline() throws Exception {
			// Create a default pipeline implementation.
			ChannelPipeline pipeline = Channels.pipeline();
			pipeline.addLast("framer", new DelimiterBasedFrameDecoder(recvBufferSize, Delimiters.lineDelimiter()));
			pipeline.addLast("decoder", new StringDecoder());
			pipeline.addLast("encoder", new StringEncoder());

			pipeline.addLast("handler", new UdpClientHandler());
			return pipeline;
		}
	}

	/**
	 * 创建连接
	 */
	private void connect() {
		log.debug("connect. ip:" + ip + ",port:" + port);
		try {
			if (channel != null) {
				channel.close();
			}
			DatagramChannelFactory factory = new NioDatagramChannelFactory(Executors.newCachedThreadPool());
			ConnectionlessBootstrap bootstrap = new ConnectionlessBootstrap(factory);
			bootstrap.setPipelineFactory(CODEC_PROTOBUF == codec ? new DataBufPipelineFactory() : new StringPipelineFactory());
			bootstrap.setOption(GlobalConstants.NETTY_RECEIVE_BUFFER_SIZE_PREDICTOR_FACTORY, new FixedReceiveBufferSizePredictorFactory(recvBufferSize));
			channel = bootstrap.connect(new InetSocketAddress(ip, port)).getChannel();
		} catch (Exception e) {
			log.error("connect error.+ip:" + ip + ",port:" + port);
		}
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

}
